package org.example.slave.packet;

import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import org.example.slave.constants.Capability;
import org.example.slave.util.ByteUtil;
import org.example.slave.util.MySQLPasswordEncrypter;

import java.io.ByteArrayOutputStream;
import java.security.NoSuchAlgorithmException;

import static org.example.slave.constants.MysqlConstant.MAX_PACKET_LENGTH;

/**
 * @author dingrui
 * @since 2022/2/15
 * @description
 */

/**
 * <pre>
 *     +-------+ +-------------+ +-----------------+ +-------------+ +--------------+ +---------------------------+ +-----------------------------+ +---------------------------+ +-----------------------------+
 *     | bytes | |     4       | |       4         | |      1      | |      23      | | n(Null-Terminated String) | |    n(Length Coded Binary)   | | n(Null-Terminated String) | |  n(Null-Terminated String)  |
 *     +-------+ +-------------+ +-----------------+ +-------------+ +--------------+ +---------------------------+ +-----------------------------+ +---------------------------+ +-----------------------------+
 *     | name | | client_flags | | max_packet_size | | charset_num | | filler(0x00) | |          user             | | scramble_buff (1 + x bytes) | |  databasename (optional)  | | auth plugin name (optional) |
 *     +-------+ +-------------+ +-----------------+ +-------------+ +--------------+ +---------------------------+ +-----------------------------+ +---------------------------+ +-----------------------------+
 * </pre>
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class AuthPacket extends PacketWithHeader {

    private int clientCapability = Capability.CLIENT_LONG_PASSWORD | Capability.CLIENT_LONG_FLAG
            | Capability.CLIENT_PROTOCOL_41 | Capability.CLIENT_INTERACTIVE
            | Capability.CLIENT_TRANSACTIONS | Capability.CLIENT_SECURE_CONNECTION
            | Capability.CLIENT_MULTI_STATEMENTS | Capability.CLIENT_PLUGIN_AUTH;

    private String username;
    private String password;
    private byte charsetNumber;
    private String databaseName;
    private int serverCapabilities;
    private byte[] scrumbleBuff;
    private byte[] authPluginName;

    @Override
    @SneakyThrows
    public byte[] toBytes() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 1. write client_flags
        ByteUtil.writeUnsignedIntLittleEndian(clientCapability, out); // remove
        // client_interactive
        // feature

        // 2. write max_packet_size
        ByteUtil.writeUnsignedIntLittleEndian(MAX_PACKET_LENGTH, out);
        // 3. write charset_number
        out.write(this.charsetNumber);
        // 4. write (filler) always 0x00...
        out.write(new byte[23]);
        // 5. write (Null-Terminated String) user
        ByteUtil.writeNullTerminatedString(getUsername(), out);
        // 6. write (Length Coded Binary) scramble_buff (1 + x bytes)
        if (StrUtil.isEmpty(getPassword())) {
            out.write(0x00);
        } else {
            try {
                byte[] encryptedPassword = MySQLPasswordEncrypter.scramble411(getPassword().getBytes(), scrumbleBuff);
                ByteUtil.writeBinaryCodedLengthBytes(encryptedPassword, out);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
            }
        }
        // 7 . (Null-Terminated String) databasename (optional)
        if (getDatabaseName() != null) {
            ByteUtil.writeNullTerminatedString(getDatabaseName(), out);
        }
        // 8 . (Null-Terminated String) auth plugin name (optional)
        if (getAuthPluginName() != null) {
            ByteUtil.writeNullTerminated(getAuthPluginName(), out);
        }
        // end write
        return out.toByteArray();
    }
}
